#include "directinputtype.h"
#include <iostream>
#include <utilities/utilities.h>

bool operator<(const GUID& lhs, const GUID& rhs)
{
  return memcmp(&lhs, &rhs, sizeof(GUID)) < 0;
}

std::ostream& operator<<(std::ostream& o, const GUID& i)
{
  return o << i.Data1 << "-" << i.Data2 << "-" << i.Data3 << "-"
           << (int)i.Data4[0] << (int)i.Data4[1] << "-" << (int)i.Data4[2]
           << (int)i.Data4[3] << (int)i.Data4[4] << (int)i.Data4[5]
           << (int)i.Data4[6] << (int)i.Data4[7];
}


DirectInputType::DirectInputType()
  : mDirectInput(0),
    mGamepad(0)
{
  DirectInput8Create(GetModuleHandle(0), DIRECTINPUT_VERSION, IID_IDirectInput8,
                     (VOID**)&mDirectInput, 0);

  enumerate();
}

DirectInputType::~DirectInputType()
{
  close();
}

bool DirectInputType::poll()
{
  bool ret(false);

  enumerate();
  if (isOpen())
  {
    ret = true;

    DIJOYSTATE2 state;
    if (!FAILED(mGamepad->GetDeviceState(sizeof(DIJOYSTATE2), &state)))
    {
      for (AxisSet::const_iterator it(mAxisTypes.begin()); it != mAxisTypes.end(); ++it)
      {
        int axisval(0);
        switch(*it)
        {
        case AXIS_LX:
          axisval = state.lX;
          break;
        case AXIS_LY:
          axisval = state.lY;
          break;
        case AXIS_LZ:
          axisval = state.lZ;
          break;
        case AXIS_LRX:
          axisval = state.lRx;
          break;
        case AXIS_LRY:
          axisval = state.lRy;
          break;
        case AXIS_LRZ:
          axisval = state.lRz;
          break;
        case AXIS_RGLSLIDER0:
          axisval = state.rglSlider[0];
          break;
        case AXIS_RGLSLIDER1:
          axisval = state.rglSlider[1];
          break;
        default:
          std::cout << "DirectInputType: no mapping for axis" << std::endl;
          break;
        }
        mData.setAxis(*it, axisval);
      }

      for (ButtonSet::const_iterator it(mButtonTypes.begin()); it != mButtonTypes.end(); ++it)
      {
        mData.setButton(*it, state.rgbButtons[*it]);
      }
    }
    else
    {
      close();
    }
  }

  return ret;
}

bool DirectInputType::isOpen() const
{
  return mGamepad;
}

void DirectInputType::close()
{
  if (isOpen())
  {
    mGamepad->Unacquire();
    delete mGamepad;
    mGamepad = 0;
  }
}

void DirectInputType::enumerate()
{
  if (!isOpen())
  {
    mDirectInput->EnumDevices(DI8DEVCLASS_GAMECTRL,
                              EnumJoysticksCallback,
                              this,
                              DIEDFL_ATTACHEDONLY);
  }
}

void DirectInputType::addAxis(const GUID& axisType)
{
  if (axisType == GUID_XAxis)
  {
    mAxisTypes.insert(AXIS_LX);
  }
  else if (axisType == GUID_YAxis)
  {
    mAxisTypes.insert(AXIS_LY);
  }
  else if (axisType == GUID_ZAxis)
  {
    mAxisTypes.insert(AXIS_LZ);
  }
  else if (axisType == GUID_RxAxis)
  {
    mAxisTypes.insert(AXIS_LRX);
  }
  else if (axisType == GUID_RyAxis)
  {
    mAxisTypes.insert(AXIS_LRY);
  }
  else if (axisType == GUID_RzAxis)
  {
    mAxisTypes.insert(AXIS_LRZ);
  }
  else if (axisType == GUID_Slider)
  {
    mAxisTypes.insert(AXIS_RGLSLIDER0);
    mAxisTypes.insert(AXIS_RGLSLIDER1);
  }
  else
  {
    std::cout << "DirectInputType: Unknown axis: " << axisType << std::endl;
  }
}

void DirectInputType::addButton(const std::string& buttonType)
{
  std::size_t number(buttonType.find_first_not_of("Button "));
  if (number && number != std::string::npos)
  {
    mButtonTypes.insert(Utilities::toInt(buttonType.substr(number)));
  }
  else
  {
    std::cout << "DirectInputType: Unknown button" << buttonType << std::endl;
  }
}

BOOL CALLBACK DirectInputType::EnumObjectsCallback(const DIDEVICEOBJECTINSTANCE* pdidoi,
                                                   VOID* pContext)
{
  DirectInputType* diType(reinterpret_cast<DirectInputType*>(pContext));
  if (pdidoi->dwType & DIDFT_AXIS)
  {
    DIPROPRANGE diprg;
    diprg.diph.dwSize = sizeof(DIPROPRANGE);
    diprg.diph.dwHeaderSize = sizeof(DIPROPHEADER);
    diprg.diph.dwHow = DIPH_BYID;
    diprg.lMin = -32767;
    diprg.lMax = 32767;
    diprg.diph.dwObj = pdidoi->dwType;
    diType->mGamepad->SetProperty(DIPROP_RANGE, &diprg.diph);
    diType->mGamepad->SetDataFormat(&c_dfDIJoystick2);
    std::string name(pdidoi->tszName);
    std::cout << "Discovered axis: " << name << std::endl;
    diType->addAxis(pdidoi->guidType);
  }
  else if (pdidoi->dwType & DIDFT_PSHBUTTON)
  {
    std::string name(pdidoi->tszName);
    std::cout << "Discovered button: " << name << std::endl;
    diType->addButton(name);
  }
  else
  {
    std::string name(pdidoi->tszName);
    std::cout << "Discovered other: " << name << std::endl;
  }

  return DIENUM_CONTINUE;
}

BOOL CALLBACK DirectInputType::EnumJoysticksCallback(const DIDEVICEINSTANCE* pdidInstance,
                                                     VOID* pContext)
{
  DirectInputType* diType(reinterpret_cast<DirectInputType*>(pContext));
  LPDIRECTINPUTDEVICE8 gamepad;
  BOOL failed(FAILED(diType->mDirectInput->CreateDevice(pdidInstance->guidInstance,
                                                        &gamepad,
                                                        0)));
  if (failed)
  {
    return DIENUM_STOP;
  }

  diType->mGamepad = gamepad;
  if (gamepad && gamepad->EnumObjects(EnumObjectsCallback, diType, DIDFT_ALL) == DI_OK)
  {
      diType->mGamepad->Acquire();
      diType->mGamepad->SendForceFeedbackCommand(DISFFC_RESET);
      diType->mData.setNumAxes(diType->mAxisTypes.size());
      diType->mData.setNumButtons(diType->mButtonTypes.size());

      diType->mName = pdidInstance->tszProductName;
      std::cout << "DirectInputType: Discovered " << diType->mName
                << std::endl;
  }
  else
  {
    diType->mGamepad = 0;
  }

  return DIENUM_CONTINUE;
}
